home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / DistUpgrade / DistUpgradeView.py < prev    next >
Text File  |  2009-11-02  |  14KB  |  369 lines

  1. # DistUpgradeView.py 
  2. #  
  3. #  Copyright (c) 2004,2005 Canonical
  4. #  
  5. #  Author: Michael Vogt <michael.vogt@ubuntu.com>
  6. #  This program is free software; you can redistribute it and/or 
  7. #  modify it under the terms of the GNU General Public License as 
  8. #  published by the Free Software Foundation; either version 2 of the
  9. #  License, or (at your option) any later version.
  10. #  This program is distributed in the hope that it will be useful,
  11. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. #  GNU General Public License for more details.
  14. #  You should have received a copy of the GNU General Public License
  15. #  along with this program; if not, write to the Free Software
  16. #  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  17. #  USA
  18.  
  19. from DistUpgradeGettext import gettext as _
  20. from DistUpgradeGettext import ngettext
  21. import subprocess
  22. from subprocess import Popen, PIPE
  23. import apt
  24. import os
  25. import apt_pkg 
  26. import signal
  27. import glob
  28. import select
  29.  
  30. from DistUpgradeAufs import doAufsChroot, doAufsChrootRsync
  31. from DistUpgradeApport import *
  32.  
  33.  
  34. def FuzzyTimeToStr(sec):
  35.   " return the time a bit fuzzy (no seconds if time > 60 secs "
  36.   #print "FuzzyTimeToStr: ", sec
  37.   sec = int(sec)
  38.  
  39.   days = sec/(60*60*24)
  40.   hours = sec/(60*60) % 24
  41.   minutes = (sec/60) % 60
  42.   seconds = sec % 60
  43.   # 0 seonds remaining looks wrong and its "fuzzy" anyway
  44.   if seconds == 0:
  45.     seconds = 1
  46.  
  47.   # string map to make the re-ordering possible
  48.   map = { "str_days" : "",
  49.           "str_hours" : "",
  50.           "str_minutes" : "",
  51.           "str_seconds" : ""
  52.         }
  53.   
  54.   # get the fragments, this is not ideal i18n wise, but its
  55.   # difficult to do it differently
  56.   if days > 0:
  57.     map["str_days"] = ngettext("%li day","%li days", days) % days
  58.   if hours > 0:
  59.     map["str_hours"] = ngettext("%li hour","%li hours", hours) % hours
  60.   if minutes > 0:
  61.     map["str_minutes"] = ngettext("%li minute","%li minutes", minutes) % minutes
  62.   map["str_seconds"] = ngettext("%li second","%li seconds", seconds) % seconds
  63.  
  64.   # now assemble the string
  65.   if days > 0:
  66.     # TRANSLATORS: you can alter the ordering of the remaining time
  67.     # information here if you shuffle %(str_days)s %(str_hours)s %(str_minutes)s
  68.     # around. Make sure to keep all '$(str_*)s' in the translated string
  69.     # and do NOT change anything appart from the ordering.
  70.     #
  71.     # %(str_hours)s will be either "1 hour" or "2 hours" depending on the
  72.     # plural form
  73.     # 
  74.     # Note: most western languages will not need to change this
  75.     return _("%(str_days)s %(str_hours)s") % map
  76.   # display no minutes for time > 3h, see LP: #144455
  77.   elif hours > 3:
  78.     return map["str_hours"]
  79.   # when we are near the end, become more precise again
  80.   elif hours > 0:
  81.     # TRANSLATORS: you can alter the ordering of the remaining time
  82.     # information here if you shuffle %(str_hours)s %(str_minutes)s
  83.     # around. Make sure to keep all '$(str_*)s' in the translated string
  84.     # and do NOT change anything appart from the ordering.
  85.     #
  86.     # %(str_hours)s will be either "1 hour" or "2 hours" depending on the
  87.     # plural form
  88.     # 
  89.     # Note: most western languages will not need to change this
  90.     return _("%(str_hours)s %(str_minutes)s") % map
  91.   elif minutes > 0:
  92.     return map["str_minutes"]
  93.   return map["str_seconds"]
  94.  
  95.  
  96. class FetchProgress(apt.progress.FetchProgress):
  97.   def __init__(self):
  98.     #print "init FetchProgress in DistUpgradeView"
  99.     apt.progress.FetchProgress.__init__(self)
  100.     self.est_speed = 0
  101.   def start(self):
  102.     self.release_file_download_error = False
  103.   def updateStatus(self, uri, descr, shortDescr, status):
  104.     # FIXME: workaround issue in libapt/python-apt that does not 
  105.     #        raise a exception if *all* files fails to download
  106.     if status == self.dlFailed:
  107.       logging.warn("updateStatus: dlFailed on '%s' " % uri)
  108.       if uri.endswith("Release.gpg") or uri.endswith("Release"):
  109.         # only care about failures from network, not gpg, bzip, those
  110.         # are different issues
  111.         for net in ["http","ftp","mirror"]:
  112.           if uri.startswith(net):
  113.             self.release_file_download_error = True
  114.             break
  115.   def pulse(self):
  116.     apt.progress.FetchProgress.pulse(self)
  117.     if self.currentCPS > self.est_speed:
  118.       self.est_speed = (self.est_speed+self.currentCPS)/2.0
  119.     return True
  120.   def estimatedDownloadTime(self, requiredDownload):
  121.     """ get the estimated download time """
  122.     if self.est_speed == 0:
  123.       timeModem = requiredDownload/(56*1024/8)  # 56 kbit 
  124.       timeDSL = requiredDownload/(1024*1024/8)  # 1Mbit = 1024 kbit
  125.       s= _("This download will take about %s with a 1Mbit DSL connection "
  126.            "and about %s with a 56k modem.") % (FuzzyTimeToStr(timeDSL), FuzzyTimeToStr(timeModem))
  127.       return s
  128.     # if we have a estimated speed, use it
  129.     s = _("This download will take about %s with your connection. ") % FuzzyTimeToStr(requiredDownload/self.est_speed)
  130.     return s
  131.     
  132.  
  133.  
  134. class InstallProgress(apt.progress.InstallProgress):
  135.   """ Base class for InstallProgress that supports some fancy
  136.       stuff like apport integration
  137.   """
  138.   def __init__(self):
  139.     apt.progress.InstallProgress.__init__(self)
  140.     self.master_fd = None
  141.  
  142.   def waitChild(self):
  143.       """Wait for child progress to exit.
  144.  
  145.       The return values is the full status returned from os.waitpid()
  146.       (not only the return code).
  147.       """
  148.       while True:
  149.           try:
  150.               select.select([self.statusfd], [], [], self.selectTimeout)
  151.           except select.error, (errno_, errstr):
  152.               if errno_ != errno.EINTR:
  153.                   raise
  154.           self.updateInterface()
  155.           try:
  156.               (pid, res) = os.waitpid(self.child_pid, os.WNOHANG)
  157.               if pid == self.child_pid:
  158.                   break
  159.           except OSError, (errno_, errstr):
  160.               if errno_ != errno.EINTR:
  161.                   raise
  162.               if errno_ == errno.ECHILD:
  163.                   break
  164.       return res
  165.  
  166.   def run(self, pm):
  167.     pid = self.fork()
  168.     if pid == 0:
  169.       # check if we need to setup/enable the aufs chroot stuff
  170.       if "RELEASE_UPGRADE_USE_AUFS_CHROOT" in os.environ:
  171.         if not doAufsChroot(os.environ["RELEASE_UPGRADE_AUFS_RWDIR"],
  172.                             os.environ["RELEASE_UPGRADE_USE_AUFS_CHROOT"]):
  173.           print "ERROR: failed to setup aufs chroot overlay"
  174.           os._exit(1)
  175.       # child, ignore sigpipe, there are broken scripts out there
  176.       # like etckeeper (LP: #283642)
  177.       signal.signal(signal.SIGPIPE,signal.SIG_IGN) 
  178.       try:
  179.         res = pm.DoInstall(self.writefd)
  180.       except Exception, e:
  181.         print "Exception during pm.DoInstall(): ", e
  182.         logging.exception("Exception during pm.DoInstall()")
  183.         os._exit(pm.ResultFailed)
  184.       os._exit(res)
  185.     self.child_pid = pid
  186.     res = os.WEXITSTATUS(self.waitChild())
  187.     # check if we want to sync the changes back, *only* do that
  188.     # if res is positive
  189.     if (res == 0 and
  190.         "RELEASE_UPGRADE_RSYNC_AUFS_CHROOT" in os.environ):
  191.       logging.info("doing rsync commit of the update")
  192.       if not doAufsChrootRsync(os.environ["RELEASE_UPGRADE_USE_AUFS_CHROOT"]):
  193.         logging.error("FATAL ERROR: doAufsChrootRsync() returned FALSE")
  194.         return pm.ResultFailed
  195.     return res
  196.   
  197.   def error(self, pkg, errormsg):
  198.     " install error from a package "
  199.     apt.progress.InstallProgress.error(self, pkg, errormsg)
  200.     logging.error("got an error from dpkg for pkg: '%s': '%s'" % (pkg, errormsg))
  201.     if "/" in pkg:
  202.       pkg = os.path.basename(pkg)
  203.     if "_" in pkg:
  204.       pkg = pkg.split("_")[0]
  205.     # now run apport
  206.     apport_pkgfailure(pkg, errormsg)
  207.  
  208. class DumbTerminal(object):
  209.     def call(self, cmd, hidden=False):
  210.         " expects a command in the subprocess style (as a list) "
  211.         import subprocess
  212.         subprocess.call(cmd)
  213.  
  214.  
  215. (STEP_PREPARE,
  216.  STEP_MODIFY_SOURCES,
  217.  STEP_FETCH,
  218.  STEP_INSTALL,
  219.  STEP_CLEANUP,
  220.  STEP_REBOOT,
  221.  STEP_N) = range(1,8)
  222.  
  223. ( _("Preparing to upgrade"),
  224.   _("Getting new software channels"),
  225.   _("Getting new packages"),
  226.   _("Installing the upgrades"),
  227.   _("Cleaning up"),
  228. )
  229.  
  230. class DistUpgradeView(object):
  231.     " abstraction for the upgrade view "
  232.     def __init__(self):
  233.         pass
  234.     def getOpCacheProgress(self):
  235.         " return a OpProgress() subclass for the given graphic"
  236.         return apt.progress.OpProgress()
  237.     def getFetchProgress(self):
  238.         " return a fetch progress object "
  239.         return FetchProgress()
  240.     def getInstallProgress(self, cache=None):
  241.         " return a install progress object "
  242.         return InstallProgress()
  243.     def getTerminal(self):
  244.         return DumbTerminal()
  245.     def updateStatus(self, msg):
  246.         """ update the current status of the distUpgrade based
  247.             on the current view
  248.         """
  249.         pass
  250.     def abort(self):
  251.         """ provide a visual feedback that the upgrade was aborted """
  252.         pass
  253.     def setStep(self, step):
  254.         """ we have 6 steps current for a upgrade:
  255.         1. Analyzing the system
  256.         2. Updating repository information
  257.         3. fetch packages
  258.         3. Performing the upgrade
  259.         4. Post upgrade stuff
  260.         5. Complete
  261.         """
  262.         pass
  263.     def hideStep(self, step):
  264.         " hide a certain step from the GUI "
  265.         pass
  266.     def showStep(self, step):
  267.         " show a certain step from the GUI "
  268.         pass
  269.     def confirmChanges(self, summary, changes, downloadSize,
  270.                        actions=None, removal_bold=True):
  271.         """ display the list of changed packages (apt.Package) and
  272.             return if the user confirms them
  273.         """
  274.         self.confirmChangesMessage = ""
  275.         self.toInstall = []
  276.         self.toUpgrade = []
  277.         self.toRemove = []
  278.         self.toDowngrade = []
  279.         for pkg in changes:
  280.             if pkg.markedInstall: self.toInstall.append(pkg.name)
  281.             elif pkg.markedUpgrade: self.toUpgrade.append(pkg.name)
  282.             elif pkg.markedDelete: self.toRemove.append(pkg.name)
  283.             elif pkg.markedDowngrade: self.toDowngrade.append(pkg.name)
  284.         # sort it
  285.         self.toInstall.sort()
  286.         self.toUpgrade.sort()
  287.         self.toRemove.sort()
  288.         self.toDowngrade.sort()
  289.         # no re-installs 
  290.         assert(len(self.toInstall)+len(self.toUpgrade)+len(self.toRemove)+len(self.toDowngrade) == len(changes))
  291.         # now build the message (the same for all frontends)
  292.         msg = "\n"
  293.         pkgs_remove = len(self.toRemove)
  294.         pkgs_inst = len(self.toInstall)
  295.         pkgs_upgrade = len(self.toUpgrade)
  296.         # FIXME: show detailed packages
  297.         if pkgs_remove > 0:
  298.           # FIXME: make those two separate lines to make it clear
  299.           #        that the "%" applies to the result of ngettext
  300.           msg += ngettext("%d package is going to be removed.",
  301.                           "%d packages are going to be removed.",
  302.                           pkgs_remove) % pkgs_remove
  303.           msg += " "
  304.         if pkgs_inst > 0:
  305.           msg += ngettext("%d new package is going to be "
  306.                           "installed.",
  307.                           "%d new packages are going to be "
  308.                           "installed.",pkgs_inst) % pkgs_inst
  309.           msg += " "
  310.         if pkgs_upgrade > 0:
  311.           msg += ngettext("%d package is going to be upgraded.",
  312.                           "%d packages are going to be upgraded.",
  313.                           pkgs_upgrade) % pkgs_upgrade
  314.           msg +=" "
  315.         if downloadSize > 0:
  316.           msg += _("\n\nYou have to download a total of %s. ") %\
  317.               apt_pkg.SizeToStr(downloadSize)
  318.           msg += self.getFetchProgress().estimatedDownloadTime(downloadSize)
  319.         if (pkgs_upgrade + pkgs_inst + pkgs_remove) > 100:
  320.           msg += "\n\n%s" % _( "Fetching and installing the upgrade "
  321.                                "can take several hours. Once the download "
  322.                                "has finished, the process cannot be cancelled.")
  323.         # Show an error if no actions are planned
  324.         if (pkgs_upgrade + pkgs_inst + pkgs_remove) < 1:
  325.           # FIXME: this should go into DistUpgradeController
  326.           summary = _("Your system is up-to-date")
  327.           msg = _("There are no upgrades available for your system. "
  328.                   "The upgrade will now be canceled.")
  329.           self.error(summary, msg)
  330.           return False
  331.         # set the message
  332.         self.confirmChangesMessage = msg
  333.         return True
  334.  
  335.     def askYesNoQuestion(self, summary, msg, default='No'):
  336.         " ask a Yes/No question and return True on 'Yes' "
  337.         pass
  338.     def confirmRestart(self):
  339.         " generic ask about the restart, can be overridden "
  340.         summary = _("Reboot required")
  341.         msg =  _("The upgrade is finished and "
  342.                  "a reboot is required. "
  343.                  "Do you want to do this "
  344.                  "now?")
  345.         return self.askYesNoQuestion(summary, msg)
  346.     def error(self, summary, msg, extended_msg=None):
  347.         " display a error "
  348.         pass
  349.     def information(self, summary, msg, extended_msg=None):
  350.         " display a information msg"
  351.         pass
  352.     def processEvents(self):
  353.         """ process gui events (to keep the gui alive during a long
  354.             computation """
  355.         pass
  356.     def showDemotions(self, summary, msg, demotions):
  357.       """
  358.       show demoted packages to the user, default implementation
  359.       is to just show a information dialog
  360.       """
  361.       self.information(summary, msg, "\n".join(demotions))
  362.  
  363. if __name__ == "__main__":
  364.   fp = FetchProgress()
  365.   fp.pulse()
  366.